Can't update state in Context - javascript

EDIT BELOW
I'm trying to migrate my states to some contexts so I don't have to constantly prop drill. The problem is, state won't update in the context. I just want a single value in state to be changed to true on click.
Here is my projectTile.js
import React, {useContext} from 'react';
import {ProjectContext, ProjectProvider} from './ProjectContext.js';
import {ProjectFunctionsContext, ProjectFunctionsProvider} from './projectFunctionsContext';
// profpic is going to be pulled from the logged in user, for demonstration purposes I just have it pulling the default profile picture right now
import profpic from '../../icon.png';
// projectImage will be passed from the project component, for now it's just a default chosen from the directory.
import defaultProjectImage from '../../defaultProject.jpg';
const ProjectTile = ({projectAuthorName, projectTitle, projectImage,setSavedProjectData}) => {
const [projects, setProjects] = useContext(ProjectContext);
const [projectClicked, setProjectClicked] = useContext(ProjectFunctionsContext);
// Console Log to try to figure out where any errors are coming from, if they arise.
console.log('Project Author: ' + projectAuthorName + " \n Project Title: " + projectTitle + " \n Project Image: " + projectImage);
// In this return statement, we build the project tile out of the elements that we're getting from the ProjectContext
console.log('projectClicked is doing this: ' + projectClicked);
return (
<div className="ProjectTile__container" onClick={() => {setProjectClicked(true); console.log(projectClicked);setSavedProjectData({projectAuthorName: projectAuthorName})}}>
<img src={defaultProjectImage /*projectImage -- this is commented out because it doesn't work at the moment*/} className="ProjectTile__Img" alt="Placeholder"/>
<div className="ProjectTile__overlay" >
<img src={profpic} className="ProjectTile__icon" alt="Profile"/>
<div className="ProjectTile__text">
{projectTitle}
<br></br>
{projectAuthorName}
</div>
</div>
</div>
);
}
export default ProjectTile;
Here is my projectFunctionsContext.js
import React, {useState, createContext} from 'react';
export const ProjectFunctionsContext = createContext();
export const ProjectFunctionsProvider = (props) => {
const [projectClicked, setProjectClicked] = useState(false);
return(
<ProjectFunctionsContext.Provider
value={[projectClicked,setProjectClicked]}
>
{props.children}
</ProjectFunctionsContext.Provider>
);
}
It just won't update projectClicked to true, what am I doing wrong?
EDIT:
Called the context in parent of this component, making it reset the state.
It happens to work with only one call to get those variables.

You need to set the values object in your ContextProvider which allows you to access the properties in your components with the useContext hook.
Your provider should look like this:
const contextValue = {
projectClicked,
setProjectClicked
};
<ProjectFunctionsContext.Provider value={contextValue}>
{props.children}
</ProjectFunctionsContext.Provider>
Then in your components use the useContext hook to retrieve the values stored in context:
const { projectClicked, setProjectClicked } = useContext(ProjectFunctionsContext);

Related

Why the button event doesnt increment the counter?

I tried to increment the counter in the test but when i press the button the value doesnt change. I used the fireEvent from React testing library and React test utils but the value still in 10.I use react 18.
CounterApp:
import {useState} from "react";
import PropTypes from "prop-types";
const CounterApp = ({value=10})=>{
const [counter,setCounter] = useState(value);
const handleAdd= ()=>{
setCounter(counter+1);
}
const handleSubstract = ()=>{
if(counter>0){
setCounter(counter-1);
}
}
const handleReset = ()=>{
setCounter(0);
}
return(
<>
<h1>CounterApp</h1>
<h2>{counter}</h2>
<button onClick={handleAdd}>+1</button>
<button onClick={handleSubstract}>-1</button>
<button onClick={handleReset}>Reset</button>
</>
);
}
CounterApp.propTypes={
value: PropTypes.number.isRequired
}
export default CounterApp;
And the test archive:
import { create} from "react-test-renderer";
import CounterApp from "../CounterApp";
import '#testing-library/jest-dom';
import ReactTestUtils from 'react-dom/test-utils';
import {fireEvent} from "#testing-library/react";
describe("Test in counterApp",()=>{
test("Should be increment the count",()=>{
const component = create(<CounterApp value={10}/>);
const values= component.root;
const button=values.findAllByType("button").at(0).props;
const counter = values.findByType("h2").props.children;
ReactTestUtils.Simulate.click(button);
expect(counter).toBe("11");
})
})
You should format your component. Otherwise it's hard to read and you'll get issues because of that.
I couldn't understand if it works fine on a manual test, so not sure if the issue is on the testing or the component itself.
When using the setter in useState you have a callback, so instead of using the getter, you should do:
const handleAdd = () => {
setCounter(prev => prev + 1);
}
For the testing you should use an id to better identify the button, not the type.
You made a mistake to update state variable using the previous state value.
ReactJS setState()
All the React components can have a state associated with them. The state of a component can change either due to a response to an action performed by the user or an event triggered by the system. Whenever the state changes, React re-renders the component to the browser. Before updating the value of the state, we need to build an initial state setup. Once we are done with it, we use the setState() method to change the state object. It ensures that the component has been updated and calls for re-rendering of the component.
setState({ stateName : updatedStateValue })
// OR
setState((prevState) => ({
stateName: prevState.stateName + 1
}))
So you should use like the following.
const handleAdd= ()=>{
setCounter(prev => prev+1);
}
const handleSubstract = ()=>{
if(counter>0){
setCounter(prev => prev-1);
}
}

How to return a list according to selected item?

I'm still a beginner in ReactJS and I'm creating a project that works with a list of pokemons. The user selects a type of pokemon, and then I must return a list according to the user's selection.
I have a list with all pokemons, but some pokemons can belong to more than one type, as shown in this example below:
Could you tell me how to create a list with only the type of pokemon that the user selected? I think I can do this using reducer(), but I have no idea how to do it.
Here's my code I put into codesandbox
import React from "react";
import { useHistory } from "react-router-dom";
import { Button } from "#material-ui/core";
import { types } from "./data";
import "./styles.css";
const App = () => {
const history = useHistory();
const handleType = (type) => {
history.push({
pathname: "/list",
state: type
});
};
return (
<div className="content">
<h3>Pokémon Types</h3>
{types.results.map((type) => (
<Button
key={type.name}
style={{
margin: "5px"
}}
variant="contained"
onClick={() => handleType(type.name)}
>
{type.name}
</Button>
))}
</div>
);
};
export default App;
import React from "react";
import { useLocation } from "react-router-dom";
import { pokemons } from "../data";
const List = () => {
const { state } = useLocation();
console.log("state: ", state);
console.log(pokemons);
return <div>List</div>;
};
export default List;
Thank you in advance for any help.
You have a lot of ways to do that, but since you are still learning and you got a nice shot of code, I will introduce useMemo for you:
you can add useMemo to memorize and process data, then get the result direct...
look at this example:
const pk = useMemo(() => {
if (!state) return "State Empty!";
let result = [];
pokemons.forEach((v, i) => {
if (v.type.includes(state)) {
result.push(<li key={v.name + i}>{v.name}</li>);
}
});
return result;
}, [pokemons, state]);
return <ul>{pk}</ul>;
By this code, I got your list, check details in a simple loop, and then retrieve the needed list...
Notes:
In key I set name and i, but it's not totally correct, but it seems there is duplication on data, and why its not totally correct?, since we need to make sure to prevent re-render when no real change, but index if order change that's mean re-render...
You can use anyway like reducer, filter, or create a separate component and put it nested of useMemo
You can enhance data style to can check or retrieve data fast by Hash table...
Demo

Getting an Empty array from the firestore

This is my code for LeftBar Component
I want to get the data stored in the "contacts" document in the firebase but getting an empty array. Idk know why this is happening. And one thing if anyone can tell me how to do it with class-based component ( how to use that use effect thing into ComponentDidMount and ComponentDidUpdate) as I have two other class Components which are also using the same functionality Please help
import React, {useState , useEffect} from "react";
import { Avatar, IconButton } from "#material-ui/core";
import AddCircleIcon from "#material-ui/icons/AddCircle";
import MoreIcon from "#material-ui/icons/More";
import ChatBubbleOutlineIcon from "#material-ui/icons/ChatBubbleOutline";
import SearchIcon from '#material-ui/icons/Search';
import LeftChats from './LeftChats';
import "./LeftBar.css";
import db from './firebase'
function LeftBar () {
const [contacts, setContacts] = useState([])
useEffect(() => {
db.collection("contacts").onSnapshot((snapshot)=> setContacts(
snapshot.docs.map((doc)=> ({
id : doc.id,
data : doc.data(),
}))
))
},[])
console.log(contacts);
return (
<div className="leftbar">
<div className="left-header">
<Avatar />
<div className="left-right-header">
<IconButton>
<AddCircleIcon />
</IconButton>
<IconButton>
<MoreIcon />
</IconButton>
<IconButton>
<ChatBubbleOutlineIcon />
</IconButton>
</div>
</div>
<div className="left-search">
<div className='input-container'>
<SearchIcon/>
<input type='text' placeholder='Search...'/>
</div>
</div>
<div className="left-chats">
{
contacts.map( contact =>(
<LeftChats key={contact.id} id = {contact.id} username=
{contact.data.name}/>
))
}
</div>
</div>
);
}
export default LeftBar;
This is my LeftChat Component
import React, { Component } from 'react'
import { Avatar} from "#material-ui/core";
class LeftChats extends Component {
constructor(props) {
super(props)
this.state = {
data : ''
}
}
render()
{
console.log(this.props)
return (
<div className='leftchats'>
<Avatar/>
<div className='chats-info'>
<h2>{this.props.username}</h2>
<p>Some text message...</p>
</div>
</div>
)
}
}
export default LeftChats
Using
<LeftChats key={contact.id} id = {contact.id} username=
{contact.data.name}/>
means that you are passing {contact.data.name} value to your custom LeftChats component. But your data prop is empty. Check your console to see whether you get data or not, since you've used console.log(this.props) inside <LeftChats />.
And for the sake of completness, you should import all from firebase, unless you know exactly what you'are doing:
import * as firebase from 'firebase';
import 'firebase/firestore';
Then have these configuration set appropriately:
const firebaseConfig = {
apiKey: "AIsdfghjzaSyC0ZHz8ooSoi05Vt2X7UL7qV9Aga1o", // use your own
authDomain: "dj-himmels.firebaseapp.com",
databaseURL: "https://dj-himmels.firebaseio.com",
projectId: "dj.himmels",
storageBucket: "dj-himmels.appspot.com",
messagingSenderId: "285437504174",
appId: "1:285566504174:web:0a9ttgb4a968acd08f7ff"
};
which is missing from your code.
In a real react native app for production, you should be using AsyncStorage to proceed your data.
Note: For your next questions try to ask one question at a time, it helps to figure out your real need and answer it accordingly. Right now you seem to be having many issues.
EDITED AFTER THE POSTER COMMENT
1- First of all, you are passing {contact.data.name} to username instead of {contacts.data.name}, because you used React hook useState to define contacts as const [contacts, setContacts] = useState([]) and not as const [contact, setContact] = useState([])
2- Second of all, if at all the
<LeftChats key={contact.id} id = {contact.id} username=
{contact.data.name}/>
component found the {contacts.data.name}, value to pass to username= contacts.data.name}, you will still get empty result because using useState([]) inside const [contacts, setContacts] = useState([]) means that you have set Contact value to empty array.
Nevertheless, you have tried to send value to Contact using React
useEffect hook like so useEffect(() => {
db.collection("contacts").onSnapshot((snapshot)=> setContacts(
snapshot.docs.map((doc)=> ({
id : doc.id,
data : doc.data(),
}))
))
},[]),
which was not triggered before component receives the initial empty array of contact. And your data value fetched from firebase may also be empty, that's why I meant by you are having many issues in the same time.
MY SUGGESTION:
1- Console.log the doc value to check whether it is empty or not. If so, then the problem is in the snapshot.docs.map((doc) otherwise it may be in the empty data inside the
this.state = {
data : ''
}
I make suggestions because I don't really know your real architecture. Also take a look at the react useEffect hooks here You may need to set a conditional value inside the [] of your
useEffect(() => {
},[x])
to reset Contact after that condition. Ultimately remember that there is still a great amount of value in sticking with functional components rather than going back to the class based components. Hope it help !

Using a global object in React Context that is not related to state

I want to have a global object that is available to my app where I can retrieve the value anywhere and also set a new value anywhere. Currently I have only used Context for values that are related to state i.e something needs to render again when the value changes. For example:
import React from 'react';
const TokenContext = React.createContext({
token: null,
setToken: () => {}
});
export default TokenContext;
import React, { useState } from 'react';
import './App.css';
import Title from './Title';
import TokenContext from './TokenContext';
function App() {
const [token, setToken] = useState(null);
return(
<TokenContext.Provider value={{ token, setToken }}>
<Title />
</TokenContext.Provider>
);
}
export default App;
How would I approach this if I just want to store a JS object in context (not a state) and also change the value anywhere?
The global context concept in React world was born to resolve problem with passing down props via multiple component layer. And when working with React, we want to re-render whenever "data source" changes. One way data binding in React makes this flow easier to code, debug and maintain as well.
So what is your specific purpose of store a global object and for nothing happen when that object got changes? If nothing re-render whenever it changes, so what is the main use of it?
Prevent re-render in React has multiple ways like useEffect or old shouldComponentUpdate method. I think they can help if your main idea is just prevent re-render in some very specific cases.
Use it as state management libraries like Redux.
You have a global object (store) and you query the value through context, but you also need to add forceUpdate() because mutating the object won't trigger a render as its not part of React API:
const globalObject = { counter: 0 };
const Context = React.createContext(globalObject);
const Consumer = () => {
const [, render] = useReducer(p => !p, false);
const store = useContext(Context);
const onClick = () => {
store.counter = store.counter + 1;
render();
};
return (
<>
<button onClick={onClick}>Render</button>
<div>{globalObject.counter}</div>
</>
);
};
const App = () => {
return (
<Context.Provider value={globalObject}>
<Consumer />
</Context.Provider>
);
};

React: store uploaded array into global variable?

I'm currently working on using React to upload a CSV file and convert the data to an array so I can access phone numbers. I've actually got it almost completely functional, with just one problem: I can't figure out how to store the array properly in a variable (dataDump) on the global level. It stores it inside another array.
Here's a picture of my console so you can see what I mean.
I'm able to access the contents of dataDump if I use dataDump[0] (as seen in the function for handleClick), but that won't work for a global variable. I need to be able to send the array's values to other components/files, so I don't think having to call it like that will work. Chances are I'm over-complicating this in my head and the answer is incredibly simple, but I've spent the past 2-3 weeks learning React, Twilio, Mongodb etc. from scratch so my brain's not cooperating.
I'll appreciate any help! Thanks! Code below. (Note this is a component that's imported to the App page.)
import React from "react";
import CSVReader from "react-csv-reader";
var dataDump = [];
console.log(dataDump);
const papaparseOptions = {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
transformHeader: header => header.toLowerCase().replace(/\W/g, "_"),
complete: function(results) {
dataDump.push(results.data);
console.log(dataDump);
var rows = results.data;
let numbers = rows.map(a => a.phone_number); //make the results ONLY the phone numbers
// console.log(numbers);
document.getElementById("data2").innerHTML=numbers; //display the phone numbers
}
};
class Import extends React.Component {
constructor(props) {
super(props);
this.state = {data:[]};
this.handleClick = this.handleClick.bind(this);
}
handleForce = data => {
// console.log(data.length);
console.log(data);
this.setState({data: data});
};
handleClick = () => {
console.log("success");
console.log(this.state.data);
console.log("Next is Numbies:");
let numbies = dataDump[0].map(a => a.phone_number);
console.log(numbies);
document.getElementById("data").innerHTML=numbies;
}
render() {
return (
<div className="container">
<CSVReader
className="csv-input"
label="Select CSV file to import"
onFileLoaded={this.handleForce}
parserOptions={papaparseOptions}
/>
<div>
</div>
<button onClick={this.handleClick.bind(this)}>
Test
</button>
<div id="data" />
<div id="data2" />
<div id="data3">
</div>
</div>
);
}
}
export default Import;
// export default DataController;
Under the hood React-Redux is using context and hooks these days, so don't bother implementing a Redux stack until you've outgrown the simpler, React API, or at least you've fixed your issue. Folks joke that Redux is like shooting a fly with a bazooka. More info on React-Redux internals here and here's the documentation for React's Context.
Some psuedo-code to get you on the right path:
// context.js
import { createContext } from 'react';
export const Store = createContext();
// app.js
import React from 'react';
import { Store } from './context';
import Import from './import'; // I wouldn't change the casing on or reuse a reserved keyword personally, maybe calling this something like 'CsvImporter' would be an improvement
function App() {
const [dataDump, setDataDump] = React.useState([]);
return (
<Store.Provider value={{ dataDump, setDataDump }}>
<Import dataDump={dataDump} setDataDump={setDataDump} />
</Store.Provider>
);
}
Now your import component has two new props, dataDump and setDataDump. You can call setDataDump just like any other call to setting state. Nice!
So you need the dataDump in a new component? That's easy peasy, lemon squeezy, and all without global variables or tossing module scoping to the side:
// foobar.js
import React from 'react';
import { Store } from './context';
export function Foobar() {
// you probably want to do more than force render an array as a string, but this is just a proof of concept
return (
<Store.Consumer>
{({ dataDump, setDataDump }) => (
<p>
`${dataDump}`
</p>
)}
</Store.Consumer>
);
}
Just make sure that Foobar or other components are rendered as children of the Provider in app.js and now you have a 'global' context for passing around dataDumps.

Categories

Resources