Why I can't update state with Context API - reactjs

I Make Kakaotalk with ReactJS
I make Search User Page now, and I use Context API because use Global State
addFriend/Index.tsx
import Header from "components/friends/Header";
import InputUserId from "./InputUserId";
import Profile from "./Profile";
import { AddFriendProvider } from "./AddFriendContext";
const AddFriend = () => {
<>
<AddFriendProvider>
<Header />
<InputUserId />
<Profile />
</AddFriendProvider>
</>;
};
export default AddFriend;
addFriend/AddFriendContext.tsx
import React, { createContext, useState } from "react";
const AddFriendContext = createContext<any>({
state: {
userId: "",
searchResult: "",
},
actions: {
setUserId: () => {},
setSearchResult: () => {},
},
});
const AddFriendProvider = ({ children }: any) => {
const [userId, setUserId] = useState("");
const [searchResult, setSearchResult] = useState("");
const value = {
state: { userId, searchResult },
actions: { setUserId, setSearchResult },
};
console.log(value);
return (
<AddFriendContext.Provider value={value}>
{console.log(setUserId, setSearchResult)}
{children}
</AddFriendContext.Provider>
);
};
export { AddFriendProvider };
export default AddFriendContext;
addFriend/InputUserId.tsx
import styled from "styled-components";
import Input from "components/common/Input";
import { ChangeEvent, useContext } from "react";
import AddFriendContext from "./AddFriendContext";
const StyledInputWrapper = styled.div`
width: 370px;
height: 80px;
display: flex;
align-items: center;
`;
const InputUserId = () => {
const { state, actions } = useContext(AddFriendContext);
console.log(state, actions);
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
actions.setUserId(value);
};
const inputStyle = {
width: "100%",
height: "40px",
paddingLeft: "10px",
fontWeight: "bold",
};
const subInputStyle = {
borderBottom: "2px solid lightgray",
focus: "2px solid black",
};
return (
<StyledInputWrapper>
<Input
type="text"
placeholder="카카오톡 아이디를 입력해주세요."
required
inputStyle={inputStyle}
subInputStyle={subInputStyle}
value={state.userId}
onChange={onChange}
/>
</StyledInputWrapper>
);
};
export default InputUserId;
If i change input element in InputUserId.tsx call actions.setUserId(value) but It not worked.
I think login is >
When Change Input, call actions.setUserId and update state.userId
Change Input value when state.userId Update
But it now Worked..
Help me and if some trouble in my code feedback me plz. thanks.

Related

Dynamically updating dropdown menu in React

Using fetch, I want to dynamically populate the City material-ui dropdwon (Select) when I select a value from the State dropdown, but could not do so. When I do the same without using the fetch, it works fine. I think the problem is with the promise being returned by the fetch call. There is no problem in the fetch call as I can see the list of cities in return. Please suggest how to do it.
import React from 'react';
import { createStyles, makeStyles, Theme } from '#material-ui/core/styles';
import InputLabel from '#material-ui/core/InputLabel';
import FormHelperText from '#material-ui/core/FormHelperText';
import FormControl from '#material-ui/core/FormControl';
import {Select, MenuItem} from '#material-ui/core';
import './App.css';
export function getStates() {
return [
{name: 'California', id: "1"},
{name: 'New York', id: "2"},
]
}
function Home() {
const useStyles = makeStyles((theme: Theme) =>
createStyles({
formControl: {
margin: theme.spacing(1),
minWidth: 120,
},
selectEmpty: {
marginTop: theme.spacing(2),
},
}),
);
const [State, setState] = React.useState([]);
const [cities, setCities] = React.useState([]);
const selectStyle = makeStyles(theme => ({
root: {
textDecoration: 'none',
color: 'red',
alignItems: 'center',
fontWeight: "bold",
display: "flex",
justifyContent: "space-around",
fontSize: 18,
margin: 0,
'&:hover': {
textDecoration: 'none'
}
},
}));
function getCities() {
var s = '' // JSON body goes here
const fetchData = async () => {
const cities = [];
try {
const res = await fetch('http://127.0.0.1:8080',
{
method : "POST",
headers: {"content-type": "text/plain"},
body: s
}
);
const data = await res.json();
console.log("state response status: " + res.status)
for(var key in data.cities) {
cities.push({id: key, name: data.cities[key]})
}
return cities;
}
catch (err) {
console.log("Fetch Exception: " + err)
}
}
const cities = fetchData();
return cities;
}
const handleStateChange = (event: React.ChangeEvent< { value: unknown} >) => {
setState(event.target.value);
const r = getCities();
setCities([r]);
}
const fixed_states = getStates();
const classes = useStyles()
const selectClass = selectStyle()
return (
<div className="main-select">
<container>
<FormControl required className={classes.formControl}>
<InputLabel id="sel">State</InputLabel>
<Select labelId="state_select_labelid" id="state_select_id" name="state_select_name" onChange={handleStateChange} className={selectClass.root}>
{fixed_states.map(({id, name}, index) => (
< MenuItem key={id} value={name}>
{name}
</MenuItem>
)) }
</Select>
<FormHelperText></FormHelperText>
</FormControl>
<FormControl required className={classes.formControl}>
<InputLabel id="city_input_label_id">City</InputLabel>
<Select labelId="city_select_labelid" id="city_select_id" name="city_select_name">
{cities.map(({id, name}, index) => (
< MenuItem key={id} value={name}>
{name}
</MenuItem>
))}
</Select>
<FormHelperText></FormHelperText>
</FormControl>
</container>
</div>
);
}
export default Home;
You code:
const handleStateChange = (event: React.ChangeEvent< { value: unknown} >) => {
setState(event.target.value);
const r = getCities();
setCities([r]);
}
but getCities return array of cities and then you set array cities in state like array of array.
So just update argument in setCities row to
const handleStateChange = (event: React.ChangeEvent< { value: unknown} >) => {
setState(event.target.value);
const r = getCities();
setCities(r);
}

Having trouble changing pressable color in react native. I had it working without it being an array, am I missing something obvious?

The goal I have is to click on the button and for the button to change color until I click it again. I had the code working with a single button but when I tried to make an array of buttons I ran into trouble, I feel like I misses something obvious but can't find it
//GRID.js
import React, { useState, useEffect } from "react";
import { Cell } from "./cell";
export const Grid = () => {
const ARRAYLENGTH = 3;
const [grid, setGrid] = useState(Array(ARRAYLENGTH).fill({ show: true }));
useEffect(() => {
console.log("updated");
}, [grid]);
const handlePress = (index) => {
let tempGrid = grid;
tempGrid[index].show = !tempGrid[index].show;
setGrid(tempGrid);
console.log(grid[index].show);
logGrid();
};
const logGrid = () => {
console.log(grid);
};
//Renders cell.js
return grid.map((item, index) => {
return (
<Cell
key={`${item} ${index}`}
show={grid[index].show}
index={index}
onClick={handlePress}
/>
);
});
};
//CELL.JS
import React, { useState, useEffect } from "react";
import styled from "styled-components/native";
import { View, Pressable } from "react-native";
import * as theme from "../../config/theme";
//Styles
const StyledCell = styled(Pressable)`
padding: 30px;
border-color: black;
border-width: 1px;
background-color: ${(props) => props.color};
`;
Here is the updated code for anyone who would like to see it
//grid.js
import React, { useState } from "react";
import { Cell } from "./cell";
export const Grid = () => {
const ARRAYLENGTH = 4;
const [grid, setGrid] = useState(
Array(ARRAYLENGTH)
.fill()
.map(() => ({ show: true }))
);
const handlePress = (index) => {
let tempGrid = [...grid];
tempGrid[index].show = !tempGrid[index].show;
setGrid(tempGrid);
};
return grid.map((item, index) => {
return (
<Cell
key={`${item} ${index}`}
show={item.show}
index={index}
onClick={handlePress}
/>
);
});
};
//cell.js
import React, { useState, useEffect } from "react";
import styled from "styled-components/native";
import { View, Pressable } from "react-native";
import * as theme from "../../config/theme";
const StyledCell = styled(Pressable)`
padding: 30px;
border-color: black;
border-width: 1px;
background-color: ${(props) => props.color};
`;
export const Cell = ({ show, onClick, index }) => {
const [color, setColor] = useState(theme.blue);
useEffect(() => {
if (show === true) {
setColor(theme.blue);
} else {
setColor(theme.white);
}
}, [show]);
return (
<View>
<StyledCell
color={color}
onPress={() => {
onClick(index);
}}
/>
</View>
);
};

next JS apollo Link change clears query, have to refresh page for query to run

I am using Next JS with Apollo and I hve the following query that works just fine, but when I navigate away from the page to another page and back the query doesn't run my fields are empty and I have to hit refresh to populate them. Does anyone know why this may be.
import { useQuery, gql, useMutation } from "#apollo/client";
import { useEffect, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import Form from "../components/Form";
import Layout from "../components/Layout";
export const INPUT_VALUES = gql`
query GetInputValues {
allFormInputVals {
data {
name
_id
type
index
}
}
}
`;
const FormBuilder = () => {
const blankFormInput = {
__typename: "FormInputVal",
name: "test",
_id: uuidv4(),
type: "text",
};
const [formState, setFormState] = useState([blankFormInput]);
const { loading, error, data } = useQuery(INPUT_VALUES);
useEffect(() => {
const formData = data?.allFormInputVals?.data;
setFormState(formData);
}, [data]);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<Layout>
{formState && <Form formState={formState} setFormState={setFormState} />}
</Layout>
);
};
export default FormBuilder;
UPDATE: as requested here is the form componenet
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { useQuery, gql, useMutation } from "#apollo/client";
import { v4 as uuidv4 } from "uuid";
import styled from "styled-components";
import { INPUT_VALUES } from "../pages/formBuilder";
import Link from "next/link";
import useSWR from "swr";
const ADD_INPUT_VALUES = gql`
mutation AddInputValues($name: String!, $type: String!, $index: Int!) {
createFormInputVal(data: { name: $name, index: $index, type: $type }) {
name
type
index
_id
}
}
`;
const UPDATE_FORM_INPUT_VAL = gql`
mutation UpdateFormInputVal(
$name: String!
$type: String!
$index: Int!
$ID: ID!
$arrayOrder: Int
) {
updateFormInputVal(
id: $ID
data: { name: $name, type: $type, index: $index, arrayOrder: $arrayOrder }
) {
name
type
index
arrayOrder
}
}
`;
const DELETE_FORM_INPUT_VAL = gql`
mutation DeleteFormInputVal($ID: ID!) {
deleteFormInputVal(id: $ID) {
name
}
}
`;
const FormStyles = styled.form`
display: grid;
grid-template-columns: 350px 1fr 1fr;
label {
//background: red;
}
input,
select {
//background: aqua;
border: 1px solid grey;
border-top: 0;
border-right: 0;
border-left: 0;
}
input[type="button"] {
background: green;
border: none;
border-radius: 30px;
color: #fff;
padding: 15px;
width: 200px;
margin: 0 0 25px auto;
&:hover {
cursor: pointer;
}
}
button {
background: lightgray;
padding: 15px;
border: none;
&:hover {
cursor: pointer;
}
}
`;
const GridStyles = styled.div`
display: grid;
grid-row-gap: 5px;
padding: 15px 0;
width: 600px;
margin: 0 auto 0 0;
div {
display: grid;
grid-template-columns: 100px 1fr;
align-items: center;
}
`;
const AddFormStyles = styled.form`
display: grid;
grid-template-columns: 1fr 1fr;
`;
const fetcher = (url) => fetch(url).then((r) => r.json());
export default function Form({ formState, setFormState }) {
const test = formState?.reduce((obj, item, idx) => {
return { ...obj, [`name-${item._id}`]: item.name };
}, {});
const { register, handleSubmit, errors } = useForm({ defaultValues: test });
const {
register: register2,
handleSubmit: handleSubmit2,
errors: errors2,
} = useForm();
const [formStateVals, setFormStateVals] = useState(undefined);
const [savingState, setSavingState] = useState(false);
const [deletingState, setDeletingState] = useState(false);
const { data: formEntryData, error } = useSWR("/api/data", fetcher);
// console.log(test);
const onSubmit = async (data) => {
let hmm = formState.map((ok, i) => {
//console.log(ok._id);
var name = data[`name-${ok._id}`];
var type = data[`type-${ok._id}`];
var boogie = {
_id: ok._id,
name: name,
type: type,
index: i,
arrayOrder: ok.arrayOrder,
};
return boogie;
});
//1. query all formEntryData *
//2. Grab all the submitted fields
//3. run a series of replaces with the correct formEntryData
//4. as well as the updateField change
// const letsGo = {
// formEntryData,
// hmm,
// };
// const res1 = await fetch("../api/update", {
// method: "POST",
// headers: {
// "Content-Type": "application/json",
// },
// body: JSON.stringify(letsGo),
// });
// console.log(formEntryData);
// console.log(hmm);
hmm.map(async (item, i) => {
const res = await updateFormInputVal({
variables: {
name: item.name,
type: item.type,
index: item.index,
ID: item._id,
arrayOrder: i,
},
}).catch(console.error);
//console.log(res);
});
setSavingState(true);
};
//console.log(errors);
const addInput = async (clickData) => {
console.log("data");
console.log(clickData);
console.log("data");
const res = await createFormInputVal({
variables: {
name: "test",
type: clickData.chooseType,
index: 0,
},
})
.then((data) => {
const blanktext = {
__typename: "FormInputVal",
name: "Test",
_id: data.data.createFormInputVal._id,
type: clickData.chooseType,
};
console.log(blanktext);
setFormState([...formState, { ...blanktext }]);
})
.catch(console.error);
};
const deleteVal = async (id) => {
setDeletingState(true);
const res = await deleteFormInputVal({
variables: {
ID: id,
},
}).catch(console.error);
console.log(res);
};
const [createFormInputVal, { data: createInputData }] = useMutation(
ADD_INPUT_VALUES
);
const [
updateFormInputVal,
{ data: updateInputData, loading: saving },
] = useMutation(UPDATE_FORM_INPUT_VAL);
const [
deleteFormInputVal,
{ data: deleteInputData, loading: deleting },
] = useMutation(DELETE_FORM_INPUT_VAL, {
refetchQueries: [{ query: INPUT_VALUES }],
});
// console.log(updateInputData);
return (
<>
<FormStyles onSubmit={handleSubmit(onSubmit)}>
<h1>Create Your Form Input Fields</h1>
<div>
{formState?.map((val, idx) => {
const nameId = `name-${val._id}`;
const typeId = `type-${val._id}`;
return (
<div key={val._id}>
<GridStyles>
<div>
<label htmlFor={nameId}>Input Name</label>
<input
type="text"
name={nameId}
id={nameId}
className={val.type}
ref={register()}
/>
</div>
<div>
{val.type}
{/* <label htmlFor={typeId}>Input Type</label>
<select
defaultValue={val.type}
name={typeId}
ref={register}
>
<option value="text">text</option>
<option value=" image"> image</option>
</select> */}
</div>
<button onClick={() => deleteVal(val._id)} type="button">
Delete
</button>
</GridStyles>
</div>
);
})}
<GridStyles>
{savingState ? saving ? <p>saving</p> : <p>saved</p> : null}
{deletingState ? deleting ? <p>deleting</p> : null : null}
<button type="submit">Save Form</button>
</GridStyles>
</div>
</FormStyles>
<AddFormStyles onSubmit={handleSubmit2(addInput)}>
<div>
<label htmlFor="chooseType">Input Type</label>
<select name="chooseType" ref={register2}>
<option value="text">text</option>
<option value="image"> image</option>
</select>
</div>
<button type="submit">Add Form Inputs</button>
</AddFormStyles>
</>
);
}
We proved (see comments) that data derived from useQuery is logged properly and passed into <Form/> (child component).
The problem was in not reinitializing form state [managing/helper] hook (useForm). It should be solved with effect:
const { register, handleSubmit, errors, reset } = useForm({ defaultValues: test });
useEffect(() => {
reset(defaultValues: test);
}, [formState])
This hooke resets form state/data on formState prop change.

How to rerender value of child from HOC

I want to have a button submiting to stripe a specific quantity, based on an input that can be changed by typing or clicking an increment/decrease button. I have the increment/decrease functions on an higher order component but i am unable to submit it to the button of stripe (child from my understanding)
Relevant code below:
withCheckout (HOC):
import React from "react"
const UpdatedComponent = (OriginalComponent) => {
class NewComponent extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 1
}
this.handleChange = this.handleChange.bind(this)
}
increment = () => {
this.setState(prevState => {
return { count: prevState.count + 1}
})
}
decrease = () => {
this.setState(prevState => {
return { count: prevState.count - 1 }
})
}
handleChange(event) {
this.setState({
count: parseInt(event.target.value)
});
}
render() {
return <OriginalComponent count={this.state.count} increment={this.increment} decrease={this.decrease} handleChange={this.handleChange} />
}
}
return NewComponent
}
export default UpdatedComponent
Checkout:
import React, { useState } from "react"
import { loadStripe } from "#stripe/stripe-js"
import UpdatedComponent from "./withCheckout"
const buttonStyles = {
fontSize: "13px",
textAlign: "center",
color: "#000",
padding: "12px 60px",
boxShadow: "2px 5px 10px rgba(0,0,0,.1)",
backgroundColor: "rgb(255, 178, 56)",
borderRadius: "6px",
letterSpacing: "0.2ch",
display: "block",
margin: "1.5em auto 1.5em auto",
}
const buttonDisabledStyles = {
opacity: "0.5",
cursor: "not-allowed",
}
let stripePromise
const getStripe = () => {
if (!stripePromise) {
stripePromise = loadStripe("KEY_HERE")
}
return stripePromise
}
const Checkout = props => {
const [loading, setLoading] = useState(false)
const redirectToCheckout = async event => {
event.preventDefault()
setLoading(true)
const stripe = await getStripe()
const { error } = await stripe.redirectToCheckout({
mode: "payment",
lineItems: [{ price: "PRICE_ID_HERE", quantity: props.count }],
successUrl: `http://localhost:8000/page-2/`,
cancelUrl: `http://localhost:8000/`,
shippingAddressCollection: {
allowedCountries: ['PT'],
}
})
if (error) {
console.warn("Error:", error)
setLoading(false)
}
}
return (
<button
disabled={loading}
style={
loading ? { ...buttonStyles, ...buttonDisabledStyles } : buttonStyles
}
onClick={redirectToCheckout}
>
Comprar {props.count}
</button>
)
}
export default UpdatedComponent(Checkout)
Example:
See this image
When I change the input to 7, I expect the button text to be "Comprar 7" and I expect to submit quantity: 7 in the striperedirect function. I think the problem has to do with the way props are set, as my Counter is working well the props were passed as const {count} = this.props. Should I add the Counter.js code as well?

How can I access state inside of my Redux cartReucer?

I need to access my current cart state which is just a list of products that have
been added to the cart, so that I check ids in order to calculate quantity for duplicate products. I realize that one of my issues here is that i've initialized itemsInCart with an empty array but i'm not sure what else to do here since, state can't be destructured without it.
cartReducer.js
const itemsInCart = []
export const cartReducer = (state = itemsInCart, action) => {
const { type, payload } = action;
switch (type) {
case "ADD_TO_CART":
return [
...state,
{
imgUrl: payload.imgUrl,
name: payload.name,
price: payload.price,
quantity: payload.quantity
},
];
default:
}
return state;
};
Product.js
Clicking the button dispatches the 'ADD_TO_CART' action, adds new products to our cart in state.
import React from 'react';
import {useDispatch} from 'react-redux';
const Product = ({imgUrl, name, price, id }) => {
const dispatch = useDispatch()
const addToCart = () => {
dispatch({
type: "ADD_TO_CART",
payload: {
imgUrl: imgUrl,
name: name,
price: price,
id: id
}
})
}
return (
<div
key={id}
style={{
textAlign: "center",
display: "flex",
border: "1px solid",
marginBottom: "2rem",
flexDirection: 'column'
}}
>
<img
src={imgUrl}
style={{ height: "5rem", width: "5rem" }}
alt="The product"
/>
<h3>{name}</h3>
<p>${price}.00</p>
{id}
<button onClick={()=>addToCart()}>Add To Cart</button>
</div>
);
}
export default Product;
InCartList.js
Renders list of items in my cart inside CartContainer
import React from "react";
import { useSelector } from "react-redux";
import CartItem from "./CartItem";
const ProductList = () => {
const allState = useSelector((state) => state.cart);
const renderProducts = () => {
return allState.map((product) => {
return (
<CartItem id={product.id} quantity={product.quantity}key={product.id} name={product.name} price={product.price}/>
);
});
};
return <>{renderProducts()}</>;
};
export default ProductList;
You shouldn't place any logic inside reducer (reducer should only pure function)
You can try to get state you want before dispatch action ADD_TO_CART
use getStateToProps function
use store which should be exported when initialized inside App component (I guess)
export const store = configureAppStore(history);

Resources