i have a an array of data in a file named 'external-data.js' like this
``
export const mydata = [
{
name: "john",
age: 20,
man: true
},
{
name: "julia",
age: 22,
man: false
}
];
``
and then i import the data in my 'reactjs' app and i tried destructuring it like so
``
import React, {component} from 'react';
import { mydata } from 'external-data.js';
class HumanApp extends Component {
state = {
myNewData: [...mydata]
}
componentDidMount() {
this.changeData();
}
const changeData = () => {
myNewData.map(item => item.name = 'boyka');
console.log("state Data: "+myNewData[0].name);
console.log("original Data:"+Mydata[0].name);
}
render() {
return (
....
);
}
};
export default HumanApp;
``
and then i expected to get: " state Data: boyka" and "original Data: john" but it seems that my changeData function also changed the original array and i get "original Data: boyka"
state = {
myNewData: [
...mydata.map(data => { ...data })
]
}
There you go:
import React, { Component } from "react";
import { mydata } from "./external-data.js";
class App extends Component {
state = {
myNewData: [...mydata]
};
componentDidMount() {
this.changeData();
}
changeData = () => {
this.setState(
(prevState) => {
const myNewData = { ...prevState.myNewData };
this.state.myNewData.forEach((item) => {
item.name = "boyka";
});
return {
myNewData
};
},
() => {
console.log(this.state.myNewData);
}
);
};
render() {
return <div>Hii</div>;
}
}
export default App;
You cannot mutate the state directly!! You need to change the state using useState() like the example above.
Related
I am trying to change the state immutably and return a new state but in the UI component new state not changed. The new state values are fetched successfully but not display. I don't understand what is the issue behind.
Anyone has suggestions share me
Here is my reducer:
import * as actionTypes from './actions';
const initialState = {
data: [
{id: 1, name: "accordion1", content: () => {}, status: 1},
{id: 2, name: "accordion2", content: () => {}, status: 0},
{id: 3, name: "accordion3", content: () => {}, status: 0},
]
}
const reducer = (state = initialState, action) => {
debugger;
switch(action.type) {
case actionTypes.ACTIVE_STATE:
debugger;
var newData = state.data;
for(var i= 0; i<newData.length; i++) {
newData[i].status = newData[i].status === 1 ? 0 : 1
}
return {
...state,
data: newData
}
default:
return state;
}
}
export default reducer;
Here is my UI component were not update:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import * as actionTypes from '../store/actions';
class Accordion extends Component {
render() {
debugger;
return (
<div>
{this.props.accordions.map((accordion, index) => {
return (
<div key={index}>
<div>{accordion.status}</div>
<div className={`accordion ${accordion.status}`} onClick={this.props.expandAccordion}>
{accordion.name}
</div>
<div className="panel">
</div>
</div>
);
})}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
accordions: state.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
expandAccordion: () => dispatch({type: actionTypes.ACTIVE_STATE})
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Accordion);
I assume that the problem is in the following lines:
var newData = state.data;
for(var i= 0; i<newData.length; i++) {
newData[i].status = newData[i].status === 1 ? 0 : 1
}
Why?
Since basically, when you assign var newData = state.data; you actually copy the object reference, and by that, you don't keep it immutable, and as far for React, which makes shallow comparing, it never changed.
One possible solution would be to change this code to an immutable update:
const newData = state.data.map((entry) => ({...entry, status: entry.status === 1 ? 0 : 1}));
return {
...state,
data: newData
}
P.S: If you want to get smarty pants, you can use XOR for your status update: ({...entry, status: entry.status ^ 1})
You are actually mutating the state. Try this...
import * as actionTypes from './actions';
const initialState = {
data: [
{id: 1, name: "accordion1", content: () => {}, status: 1},
{id: 2, name: "accordion2", content: () => {}, status: 0},
{id: 3, name: "accordion3", content: () => {}, status: 0},
]
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case actionTypes.ACTIVE_STATE:
return {
...state,
data: state.data.map((acdnObj) => {
return {
...acdnObj,
status: acdnObj.status === 1 ? 0 : 1,
}
}),
}
default:
return state;
}
}
export default reducer;
Here is my code. Been trying to get this to work for 4 days now. I've tried everything from function Components to class Components, to everything in between. At my wits' end. I can't set the state properly in the constructor because it's asynchronous but it doesn't re-render when put in componentDidMount()
EDIT: Yes i know some of the code is redundant. I changed it around to isolate the bug and removed the addParams() logic as well as some other things.
import React, { Component } from "react";
import axios from "axios";
export const ListContext = React.createContext();
class ListContextProvider extends Component {
constructor() {
super();
this.state = { cards: {}, params: {} };
}
addParams(parameters) {
return parameters;
}
getCards() {
let parameters = this.addParams({ last_name__icontains: "smith" });
console.log(parameters);
axios({
method: "get",
url: "http://127.0.0.1:8000/charges/cardsapi/",
params: parameters,
}).then((response) => {
let cards = Object.values(response.data.results);
let cardsState = Object.assign({}, cards);
this.setState({ cards: cardsState });
console.log(this);
console.log(this.state);
});
}
componentDidMount() {
console.log("mounted");
this.getCards();
}
render() {
return (
<ListContext.Provider value={this.state}>
{this.props.children}
</ListContext.Provider>
);
}
}
export { ListContextProvider };
and then the Consumer:
export default function DataTable(context) {
let cards = context;
const rows = [];
const headerCells = [];
if (cards.length > 1) {
for (let field in cards[0]) {
headerCells.push(<TableCell key={field}>{field}</TableCell>);
}
for (let row of cards) {
const cells = [];
for (const [key, value] of Object.entries(row)) {
cells.push(<TableCell key={key}>{value}</TableCell>);
}
rows.push(<TableRow key={cells[0].props.children}>{cells}</TableRow>);
}
return (
<Table>
<TableHead>
<TableRow key={"header"}>{headerCells}</TableRow>
</TableHead>
<TableBody>{rows}</TableBody>
</Table>
);
} else {
return (
<Table>
<tbody>
<tr>
<td>Empty</td>
</tr>
</tbody>
</Table>
);
}
}
This worked:
import React, { Component } from "react";
import axios from "axios";
let ListContext = React.createContext();
class ListContextProvider extends Component {
constructor() {
super();
this.state = { cards: [], params: {} };
}
addParams(parameters) {
this.setState({ params: parameters });
}
getCards() {
let parameters = this.state.params;
console.log(parameters);
axios({
method: "get",
url: "http://127.0.0.1:8000/charges/cardsapi/",
params: parameters,
}).then((response) => {
let cardsState = Object.values(response.data.results);
this.setState({ cards: cardsState });
});
}
async componentDidMount() {
await this.addParams({ last_name__icontains: "shaer" });
console.log("mounted");
this.getCards();
}
render() {
return (
<ListContext.Provider value={this.state}>
{this.props.children}
</ListContext.Provider>
);
}
}
export { ListContextProvider, ListContext };
I'm trying to change the state of only one specific array item from the reviews array. How can this be done? This code doesn't seem to work:
this.setState({
reviews[2].current: true
});
Here's the full code:
import React, { Component } from "react";
import { render } from "react-dom";
const reviewsInit = [
{
name: "Peter Lahm",
current: null
},
{
name: "Simon Arnold",
current: null
},
{
name: "Claire Pullen",
current: null
}
];
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
reviews: reviewsInit
};
}
change = () => {
this.setState({
reviews[2].current: true
});
};
render() {
return (
<div>
{console.log(this.state.reviews[2].current)}
<button onClick={this.change}>click me</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Demo: https://stackblitz.com/edit/react-tbryf5
As you can probably tell I'm new to react! Thanks for any help here
For some context, React detects state change when reference of the state object changes. It does not track deep changes happening in array or the object.
Solution
We need to make another variable with same data (mostly destructuring). Change the value needed. And assign that to state again.
For Object
this.setState({...oldState, keyToChange: 'newValue'});
For Array
const temp = [...oldState];
temp[index] = 'newValue';
this.setState(temp);
Hope it helps.
It's common for an Array state to copy first then update one of its value
change = () => {
const result = [...this.state.reviews];
result[2].current = true;
this.setState({reviews: result});
};
import React, { Component } from "react";
import { render } from "react-dom";
const reviewsInit = [
{
name: "Peter Lahm",
current: null,
},
{
name: "Simon Arnold",
current: null,
},
{
name: "Claire Pullen",
current: null,
},
];
class App extends Component {
constructor() {
super();
this.state = {
name: "React",
reviews: reviewsInit,
};
}
change = () => {
const prevState = [...this.state.reviews];
prevState[2].current = true;
this.setState({
reviews: prevState,
});
};
render() {
return (
<div>
{console.log(this.state.reviews[2].current)}
<button onClick={this.change}>click me</button>
</div>
);
}
}
render(<App />, document.getElementById("root"));
I've created a simple todo list to learn react and i'm trying to add some additional features. At the moment i'm trying to add buttons that toggle the list of items, so it either shows all the tasks or just those that are completed.
I've written a function to change the state of my visabilityFilter so I can later use this to toggle the items in the list, but it isn't behaving how it should be.
I console log the visabilityFilter variable but it always shows the wrong state before changing to the correct state. e.g. the 'show all' button will console log 'show completed' then if you press it again it will console log 'show all'
App.js
import React, { Component } from 'react';
import './App.css';
import TodoList from './components/TodoList.js'
import VisabilityFilter from './components/VisabilityFilter.js'
export const SHOW_ALL = 'show_all'
export const SHOW_COMPLETED = 'show_completed'
class App extends Component {
constructor (props) {
super(props)
this.state = {
inputValues: {
'newTodo': ''
},
todos: [
{
task: 'My First Todo',
completed: false
}
],
visabilityFilter: SHOW_ALL
}
this.addTodo = this.addTodo.bind(this)
this.handleInputChange = this.handleInputChange.bind(this)
this.handleKeyUp = this.handleKeyUp.bind(this)
this.toggleCompleted = this.toggleCompleted.bind(this)
this.removeTodo = this.removeTodo.bind(this)
this.checkCompleted = this.checkCompleted.bind(this)
this.setVisabilityFilter = this.setVisabilityFilter.bind(this)
}
handleInputChange (e) {
const { inputValues } = this.state
const { id, value } = e.target
this.setState({
inputValues: {
...inputValues,
[id]: value
}
})
}
handleKeyUp (e) {
var code = e.key
if(code === 'Enter') {
this.addTodo(e);
}
}
toggleCompleted (e, index) {
const { todos } = this.state
todos[index].completed = !todos[index].completed
todos.sort((a, b) => b.completed - a.completed)
this.setState({ todos })
}
removeTodo (e, index) {
const { todos } = this.state
this.setState ({ todos: todos.filter((todo, i) => i !== index) })
}
addTodo (e) {
const { todos, inputValues } = this.state
const { dataset } = e.target
if (inputValues[dataset.for] === '') return
const newTodo = { task: inputValues[dataset.for], completed: false }
todos.push(newTodo)
this.setState({
todos,
inputValues: { ...inputValues, [dataset.for]: '' }
})
}
checkCompleted (e, index) {
const { todos } = this.state
return { todos } && todos[index].completed
}
setVisabilityFilter (e) {
const { visabilityFilter } = this.state
const { dataset } = e.target
this.setState({
visabilityFilter: dataset.for
})
console.log ({ visabilityFilter })
}
render() {
const { todos, inputValues, visabilityFilter } = this.state
return (
<div className="App">
<TodoList
todos={todos}
inputValues={inputValues}
addTodo={this.addTodo}
handleInputChange={this.handleInputChange}
removeTodo={this.removeTodo}
toggleCompleted={this.toggleCompleted}
handleKeyUp={this.handleKeyUp}
checkCompleted={this.checkCompleted}
/>
<VisabilityFilter setVisabilityFilter={this.setVisabilityFilter} />
</div>
);
}
}
export default App;
VisabilityFilter.js
import React from 'react'
import { func } from 'prop-types'
import { SHOW_ALL, SHOW_COMPLETED } from '../App'
const VisabilityFilter = props => {
return (
<div>
<button data-for={SHOW_COMPLETED} onClick={ props.setVisabilityFilter } >
Show Completed Tasks
</button>
<button data-for={SHOW_ALL} onClick={ props.setVisabilityFilter }>
Show All Tasks
</button>
</div>
)
}
VisabilityFilter.propTypes = {
setVisabilityFilter: func.isRequired
}
export default VisabilityFilter
setState() is async (React docs), so the state changes won't be applied immediately. If you want to log out the new state,setState() takes in a function as the second argument and performs that function when the state is updated. So:
this.setState({
abc: xyz
},
() => console.log(this.state.abc),
)
Or you can also use componentDidUpdate(), which is recommended
In the functional components, you can use useEffect to track changes in state.
useEffect(() => {
console.log(someState);
},[someState);
I´m making a call to my API to get a specific object using its id.
As I call console.log in the mapStateToProps method, the entire object is printed, but the state's "anuncio" property is undefined when I try to access it with this.props.anuncio. What am I doing wrong?
Below is how I'm using the reducer and the action.
import React from 'react'
import PropTypes from 'prop-types'
import { fetchAnuncio } from '../../actions/anuncios';
import { connect } from 'react-redux';
import Avatar from 'react-avatar';
import star from '../../imgs/star.png';
class AnuncioDetalhes extends React.Component {
constructor(props) {
super(props);
this.state = {
id: this.props.anuncio ? this.props.anuncio.id : null,
img: this.props.anuncio ? this.props.anuncio.img : null
}
}
componentDidMount() {
if (this.props.match.params.id) {
this.props.fetchAnuncio(this.props.match.params.id);
}
}
render () {
const {id, img} = this.state;
return (
<div>
</div>
);
}
}
AnuncioDetalhes.propTypes = {
fetchAnuncio: PropTypes.func.isRequired,
history: PropTypes.object.isRequired
}
function mapStateToProps(state, props) {
if(props.match.params.id) {
console.log(state.anuncios.find(item => item.id === 4))
return {
anuncio: state.anuncios.find(item => item.id === props.match.params.id)
}
}
return { anuncio: null };
}
export default connect(mapStateToProps, {fetchAnuncio})(AnuncioDetalhes);
reducer:
import { SET_ANUNCIOS, ANUNCIO_FETCHED } from '../actions/types';
export default function anuncios(state = [], action = {}) {
switch(action.type) {
case ANUNCIO_FETCHED:
const index = state.findIndex(item => item.id === action.anuncio.id);
if(index > -1) {
return state.map(item => {
if (item.id === action.anuncio.id) return action.anuncio;
return item;
});
} else {
return [
...state,
action.anuncio
];
}
case SET_ANUNCIOS:
return action.anuncios;
default: return state;
}
}
action:
import axios from 'axios';
import Constants from '../components/Constants'
import { SET_ANUNCIOS, ANUNCIO_FETCHED } from './types';
export function setAnuncios(anuncios) {
return {
type: SET_ANUNCIOS,
anuncios
}
}
export function anuncioFetched(anuncio) {
return {
type: ANUNCIO_FETCHED,
anuncio
};
}
export function fetchAnuncios(cidade) {
return dispatch => {
return axios.get(Constants.BASE_URL + "anuncios/?user__cidade=" + cidade + "&status=2").then(res => {
dispatch(setAnuncios(res.data.results));
});
}
}
export function fetchAnuncio(id) {
return dispatch => {
return axios.get(Constants.BASE_URL + "anuncios/" +`${id}`).then(res => {
dispatch(anuncioFetched(res.data));
});
}
}
I think you should use componentWillReceiveProps lifecycle method to get your updated props.
componentWillReceiveProps = (nextProps) => {
console.log(nextProps)
}