Как поддерживать состояние после обновления страницы в React.js?

#javascript #reactjs #context-api

Вопрос:

Допустим, у меня есть код, который задает состояние для поля выбора, выбранного на предыдущей странице:

 this.setState({selectedOption: 5});
 

Есть ли какой-либо способ this.state.selectedOption заполнить 5 после обновления страницы?

Есть ли обратные вызовы, которые я могу использовать, чтобы сохранить это в localStorage, а затем выполнить один большой setState или есть стандартный способ сделать что-то подобное?

Комментарии:

1. gaearon.github.io/react-hot-loader

Ответ №1:

Поэтому мое решение состояло в том, чтобы также установить localStorage при настройке моего состояния, а затем localStorage снова получить значение внутри getInitialState обратного вызова, например так:

 getInitialState: function() {
    var selectedOption = localStorage.getItem( 'SelectedOption' ) || 1;

    return {
        selectedOption: selectedOption
    };
},

setSelectedOption: function( option ) {
    localStorage.setItem( 'SelectedOption', option );
    this.setState( { selectedOption: option } );
}
 

Я не уверен, можно ли считать это Анти-шаблоном, но это работает, если нет лучшего решения.

Комментарии:

1. Единственная часть этого, которую я бы счел немного «анти-шаблоном», заключается в том, что ваш компонент сохраняет свое собственное состояние внутри localStorage. Если бы этот компонент использовался несколько раз, вам пришлось бы иметь дело с конфликтами ключей. Имо, информация, которая является такой важной (должна сохраняться в локальном хранилище), вероятно, должна быть перемещена в более высокое состояние или уровень данных в архитектуре приложений… но это самый простой способ выполнить работу до тех пор, пока не будут проработаны детали

2. @CoryDanielson, каков тогда должен быть рекомендуемый способ?

3. Если вы хотите использовать localStorage для сохранения состояния таким образом, чтобы это было безопасно для нескольких экземпляров одного и того же компонента, вы можете присвоить каждому компоненту уникальный идентификатор, а затем использовать этот идентификатор в качестве ключа в localStorage, как ${this.props.id}.SelectedOption

4. Если входные данные являются частью формы (или группы связанных входных данных), вы можете сохранить все входные данные/значения в одном объекте, а затем JSON.stringify объект и сохранить строку в локальном хранилище

5. Если вы замените localStorage на sessionStorage , вы получите отдельные состояния для каждой вкладки, избегая столкновений между вкладками

Ответ №2:

Вы можете «сохранить» состояние, используя локальное хранилище, как предлагает Омар, но это следует сделать, как только состояние будет установлено. Для этого вам нужно передать setState функцию обратного вызова, а также сериализовать и десериализовать объекты, помещенные в локальное хранилище.

 constructor(props) {
  super(props);
  this.state = {
    allProjects: JSON.parse(localStorage.getItem('allProjects')) || []
  }
}


addProject = (newProject) => {
  ...

  this.setState({
    allProjects: this.state.allProjects.concat(newProject)
  },() => {
    localStorage.setItem('allProjects', JSON.stringify(this.state.allProjects))
  });
}
 

Ответ №3:

У нас есть приложение, которое позволяет пользователю устанавливать «параметры» на странице. Что мы делаем, так это устанавливаем эти параметры на URL-адрес, используя маршрутизатор React (в сочетании с историей) и библиотеку, которая кодирует объекты JavaScript с помощью URI в формат, который можно использовать в качестве строки запроса.

Когда пользователь выбирает опцию, мы можем перенести ее значение на текущий маршрут с помощью:

 history.push({pathname: 'path/', search: '?'   Qs.stringify(params)});
 

pathname может быть текущий путь. В вашем случае params это выглядело бы примерно так:

 {
  selectedOption: 5
}
 

Затем на верхнем уровне дерева React маршрутизатор React обновит props этот компонент с помощью реквизита location.search , кодированное значение которого мы установили ранее, так что будет что-то componentWillReceiveProps вроде:

 params = Qs.parse(nextProps.location.search.substring(1));
this.setState({selectedOption: params.selectedOption});
 

Затем этот компонент и его дочерние элементы будут повторно отрисованы с обновленными настройками. Поскольку информация указана на URL — адресе, ее можно добавить в закладки (или отправить по электронной почте-это был наш вариант использования), и обновление оставит приложение в том же состоянии.
Это действительно хорошо работает для нашего приложения.

Маршрутизатор реакции: https://github.com/reactjs/react-router

История: https://github.com/ReactTraining/history

Библиотека строк запроса: https://github.com/ljharb/qs

Комментарии:

1. Это должно быть приемлемым решением, а не сохранением в локальном хранилище, что не всегда возможно и замедляет работу приложения. Я попытаюсь применить это, спасибо.

2. Может ли это работать для больших массивов?

Ответ №4:

Я считаю, что состояние предназначено только для просмотра информации, а данные, которые должны сохраняться за пределами состояния представления, лучше хранить в качестве реквизитов. Параметры URL-адресов полезны, когда вы хотите иметь возможность ссылаться на страницу или делиться URL-адресом в приложении, но в противном случае загромождаете адресную строку.

Взгляните на Redux-Сохраняйтесь (если вы используете redux) https://github.com/rt2zz/redux-persist

Ответ №5:

Состояние загрузки из локального хранилища, если оно существует:

 constructor(props) {
    super(props);           
    this.state = JSON.parse(localStorage.getItem('state'))
        ? JSON.parse(localStorage.getItem('state'))
        : initialState
 

переопределение this.setState для автоматического сохранения состояния после каждого обновления :

 const orginial = this.setState;     
this.setState = function() {
    let arguments0 = arguments[0];
    let arguments1 = () => (arguments[1], localStorage.setItem('state', JSON.stringify(this.state)));
    orginial.bind(this)(arguments0, arguments1);
};
 

Ответ №6:

С крючками и хранилищем сеансов:

 const [count, setCount] = useState(1);

  useEffect(() => {
    setCount(JSON.parse(window.sessionStorage.getItem("count")));
  }, []);

  useEffect(() => {
    window.sessionStorage.setItem("count", count);
  }, [count]);

  return (
    <div className="App">
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count   1)}> </button>
    </div>
  );
 

Замените sessionStorage на localStorage, если это то, что вы все еще предпочитаете.

Комментарии:

1. Почему два эффекта использования?

2. Привет @Quimbo, кто может вспомнить… Потому что (код внутри) первого должен запускаться при каждом рендеринге, включая обновление страницы, в то время как второй должен запускаться только при count изменении

Ответ №7:

С крючками:

 const MyComponent = () => {

  const [selectedOption, setSelectedOption] = useState(1)

  useEffect(() => {
    const storedSelectedOption = parseInt(sessionStorage.getItem('selectedOption') || '1')
    setSelectedOption(storedSelectedOption)
  }, [])

  const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedOption(parseInt(e.target.value))
    sessionStorage.setItem('selectedOption', e.target.value)
  }

  return (
    <select onChange={handleOnChange}>
      <option value="5" selected={selectedOption === 5}>Five</option>
      <option value="3" selected={selectedOption === 3}>Three</option>
    </select>
  )
}
 

По-видимому, это тоже работает:

 const MyComponent = () => {

  const [selectedOption, setSelectedOption] = useState<number>(() => {
    return parseInt(sessionStorage.getItem('selectedOption') || '1')
  })

  const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    setSelectedOption(parseInt(e.target.value))
    sessionStorage.setItem('selectedOption', e.target.value)
  }

  return (
    <select onChange={handleOnChange}>
      <option value="5" selected={selectedOption === 5}>Five</option>
      <option value="3" selected={selectedOption === 3}>Three</option>
    </select>
  )
}

 

В современных браузерах:

 return (
  <select onChange={handleOnChange} value={selectedOption}>
    <option value="5">Five</option>
    <option value="3">Three</option>
  </select>
)
 

Для ввода что-то вроде этого должно работать:

(В ответ на вопрос в комментарии от @TanviAgarwal)

 const MyInput = React.forwardRef((props, ref) => {
  const { name, value, onChange } = props

  const [inputValue, setInputValue] = React.useState(() => {
    return sessionStorage.getItem(name) || value || ''
  })

  const handleOnChange = (e) => {
    onChange amp;amp; onChange(e)
    setInputValue(e.target.value)
    sessionStorage.setItem(name, e.target.value)
  }

  return <input ref={ref} {...props} value={inputValue} onChange={handleOnChange} />
})

 

(С машинописным текстом это немного сложнее, но не так ужасно)

Это сохранит значение «навсегда», поэтому вам придется как-то обрабатывать сброс формы.

Комментарии:

1. Для массива объектов существуют поля ввода с параметрами редактирования, как это можно сделать возможным? Я пробую ваш код, но после обновления значения он уходит. @tofsjonas

2. Я не уверен, что понимаю, что вы имеете в виду. Не могли бы вы привести пример кода?

3. const cols = [ { название: «Имя», поле: «имя», }, { название: «Класс», поле: «класс», } ]; таблица const= [ {имя: «Танви», класс: «старшая школа»}]; это два массива, которые я использую. Я пытаюсь создать список с полями ввода, которые можно редактировать, но после обновления данные удаляются

4. {cols.карта((e, индекс) => ><div><div><Кнопка ListItem><Кнопка ListItem><Первичный текст ListItemText={e.заголовок} /> <Первичный текст ListItemText={e.заголовок} /><div><div><ListItemSecondaryAction><ListItemSecondaryAction><Заполнитель ввода=»Введите» вариант=»в общих чертах» размер=»маленький» onChange={handleAdd} значение={Оценка редактирования} /><Заполнитель ввода=»Введите» вариант=»очерченный» размер=»маленький» onChange={handleAdd} значение={Оценка редактирования} /><Кнопка очерчена=»истинный» вариант=»содержащийся» цвет=»основной» onClick={() => Сохранить(поле)}<Кнопка очерчена=»истинный» вариант=»содержащийся» цвет=»основной» onClick= {() = > Сохранить<Кнопка очерчена=»истинный» вариант= «содержащийся» цвет= «основной» onClick= {() = > ></Кнопка ></Кнопка></ListItemSecondaryAction></ListItemSecondaryAction></div></div></ListItem>)}

5. @TanviAgarwal Я внес правку, код в комментариях не так читаем..

Ответ №8:

Я могу опоздать, но фактический код для react-create-app для react > 16 версий. После каждого изменения состояние сохраняется в хранилище сеансов (не локальном хранилище) и шифруется с помощью crypto-js. При обновлении (когда пользователь требует обновления страницы, нажав кнопку обновить) состояние загружается из хранилища. Я также рекомендую не использовать исходные карты в сборке, чтобы избежать удобочитаемости ключевых фраз.

мой index.js

 import React from "react";
import ReactDOM from "react-dom";
import './index.css';
import App from './containers/App';
import * as serviceWorker from './serviceWorker';
import {createStore} from "redux";
import {Provider} from "react-redux"
import {BrowserRouter} from "react-router-dom";
import rootReducer from "./reducers/rootReducer";
import CryptoJS from 'crypto-js';

const key = CryptoJS.enc.Utf8.parse("someRandomText_encryptionPhase");
const iv = CryptoJS.enc.Utf8.parse("someRandomIV");
const persistedState = loadFromSessionStorage();

let store = createStore(rootReducer, persistedState,
    window.__REDUX_DEVTOOLS_EXTENSION__ amp;amp; window.__REDUX_DEVTOOLS_EXTENSION__());

function loadFromSessionStorage() {
    try {
        const serializedState = sessionStorage.getItem('state');
        if (serializedState === null) {
            return undefined;
        }
        const decrypted = CryptoJS.AES.decrypt(serializedState, key, {iv: iv}).toString(CryptoJS.enc.Utf8);
        return JSON.parse(decrypted);
    } catch {
        return undefined;
    }
}

function saveToSessionStorage(state) {
        try {
            const serializedState = JSON.stringify(state);
            const encrypted = CryptoJS.AES.encrypt(serializedState, key, {iv: iv});
            sessionStorage.setItem('state', encrypted)
        } catch (e) {
            console.log(e)
        }
}

ReactDOM.render(
    <BrowserRouter>
        <Provider store={store}>
            <App/>
        </Provider>
    </BrowserRouter>,
    document.getElementById('root')
);

store.subscribe(() => saveToSessionStorage(store.getState()));

serviceWorker.unregister();
 

Комментарии:

1. в чем смысл шифрования, поскольку ключ должен быть на интерфейсе, поэтому его можно найти с помощью инструментов инспектора в браузере?

2. @MehdiSaffar Вы шифруете данные, а не ключ. Кто-то с ключом получит зашифрованные данные, но это ничего не значит, если у вас также нет секретного ключа для расшифровки данных